//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// COM classes stuff.
//

# include "StdAfx.h"
# include "COMBase.h"
# include "Reg.h"

# include <initguid.h>
# define IID_ICursor IID_ICursor_x
# include "GPSMapEditItf.h"
# include "GPSMapEditItf_i.c"

# define LOOKUP_COM_CLASSES() assert(true)

# define APPID_GPSMapEdit "{9375C706-29A7-4574-9964-1B4120E3CC04}"

# include "COMBase.inl"

class _CComClassFactory : public CComBase, public IClassFactory {
	pfCreateCOMObject_t m_pfCreateCOMObject;

  public:
	_CComClassFactory (pfCreateCOMObject_t _pfCreateCOMObject) : m_pfCreateCOMObject (_pfCreateCOMObject) {}

	// IUnknown.
	STDMETHOD_ (ULONG, AddRef)  () {return CComBase::AddRef ();}
	STDMETHOD_ (ULONG, Release) () {return CComBase::Release ();}
	STDMETHOD (QueryInterface) (REFIID _iid, void ** _ppvObject) {
		if (::IsEqualIID (_iid, IID_IClassFactory))
			* _ppvObject = static_cast<IClassFactory *> (this);
		else if (::IsEqualIID (_iid, IID_IUnknown))
			* _ppvObject = static_cast<IUnknown      *> (this);
		else {
			* _ppvObject = NULL;
			return E_NOINTERFACE;
		}
		CComBase::AddRef ();
		return S_OK;
	}

	// IClassFactory
	STDMETHOD (CreateInstance) (LPUNKNOWN _pUnkOuter, REFIID _iid, void ** _ppvObject) {
		if (_pUnkOuter)
			return CLASS_E_NOAGGREGATION;

		* _ppvObject = NULL;

		CComPtr<IUnknown> pObject;
		pObject.Attach (m_pfCreateCOMObject ());
		const HRESULT hr = pObject.p->QueryInterface (_iid, _ppvObject);
		if (FAILED (hr)) {
			delete pObject;
			return hr;
		}

		return S_OK;
	}

	STDMETHOD (LockServer) (BOOL _fLock) {
		// TODO:
		return S_OK;
	}
};

/////////////////////////////////////////////////////

static
void _RegisterCOMClass (const char * _strProgId, const char * _strCLSID) {
	CRegistryKey hProgId;
	hProgId.Create (HKEY_CLASSES_ROOT, _strProgId);
	if (! hProgId.IsValid ())
		return;

	char strCLSID [64];
	CRegistryKey hProgId_CLSID;
	const DWORD r = hProgId_CLSID.Open (hProgId, "CLSID");
	// TODO: handle ERROR_ACCESS_DENIED
	if (r == ERROR_FILE_NOT_FOUND) {
		LOOKUP_COM_CLASSES ();
		hProgId_CLSID.Create (hProgId, "CLSID");
		hProgId_CLSID.SetValue (NULL, _strCLSID);
	} else {
		if (hProgId_CLSID.QueryValue (NULL, strCLSID, sizeof (strCLSID)) == ERROR_SUCCESS)
			_strCLSID = strCLSID;
	}

	CRegistryKey hCLSID;
	hCLSID.Open (HKEY_CLASSES_ROOT, "CLSID");

	CRegistryKey hCLSID_CLSID;
	hCLSID_CLSID.Create (hCLSID, _strCLSID);
	hCLSID_CLSID.SetValue ("AppID", APPID_GPSMapEdit);

	CRegistryKey hCLSID_CLSID_LocalServer;
	hCLSID_CLSID_LocalServer.Create (hCLSID_CLSID, "LocalServer32");

	CRegistryKey hCLSID_CLSID_ProgID;
	hCLSID_CLSID_ProgID.Create (hCLSID_CLSID, "ProgID");
	hCLSID_CLSID_ProgID.SetValue (NULL, _strProgId);

	CRegistryKey hCLSID_CLSID_Programmable;
	hCLSID_CLSID_Programmable.Create (hCLSID_CLSID, "Programmable");

	char strPath [MAX_PATH + 1];
	::GetModuleFileName (NULL, strPath, MAX_PATH);

	hCLSID_CLSID_LocalServer.SetValue (NULL, strPath);
}

void RegisterCOMClasses () {
	//
	// Register the type library.
	//

	char strTypeLibPath [MAX_PATH + 1];
	::GetModuleFileName (NULL, strTypeLibPath, MAX_PATH);

	USES_CONVERSION;

	CComPtr<ITypeLib> pTypeLib;
	::LoadTypeLibEx (A2OLE (strTypeLibPath), REGKIND_REGISTER, & pTypeLib);

	//
	// Register AppID.
	//

	CRegistryKey hAppID;
	hAppID.Open (HKEY_CLASSES_ROOT, "AppID");

	CRegistryKey hAppID_AppID;
	hAppID_AppID.Create (hAppID, APPID_GPSMapEdit);
	if (hAppID_AppID.IsValid ())
		hAppID_AppID.SetValue (NULL, "GPSMapEdit");

	CRegistryKey hAppID_GPSMapEdit;
	hAppID_GPSMapEdit.Create (hAppID, "mapedit.exe");
	if (hAppID_GPSMapEdit.IsValid ())
		hAppID_GPSMapEdit.SetValue ("AppId", APPID_GPSMapEdit);

	//
	// Register COM classes.
	//

	for (const CCOMClass * pClass = CCOMClass::s_pFirst; pClass != NULL; pClass = pClass->m_pNext)
		_RegisterCOMClass (pClass->m_strProgId, pClass->m_strCLSID);
}

bool AreCOMClassesRegistered () {
	CRegistryKey hAppID;
	hAppID.Open (HKEY_CLASSES_ROOT, "AppID");

	CRegistryKey hAppID_AppID;
	hAppID_AppID.Open (hAppID, APPID_GPSMapEdit);
	return hAppID_AppID.IsValid ();
}

static
void _UnregisterCOMClass (const char * _strProgId, const char * _strCLSID) {
	LOOKUP_COM_CLASSES ();

	::SHDeleteKey (HKEY_CLASSES_ROOT, _strProgId);

	CRegistryKey hCLSID;
	hCLSID.Open (HKEY_CLASSES_ROOT, "CLSID");
	::SHDeleteKey (hCLSID, _strCLSID);
}

void UnregisterCOMClasses () {
	//
	// Unregister the type library.
	//

	::UnRegisterTypeLib (LIBID_GPSMapEdit, 1, 0, 0, SYS_WIN32);

	//
	// Register AppID.
	//

	CRegistryKey hAppID;
	hAppID.Open (HKEY_CLASSES_ROOT, "AppID");
	::SHDeleteKey (hAppID, APPID_GPSMapEdit);
	::SHDeleteKey (hAppID, "mapedit.exe");

	//
	// Unregister COM classes.
	//

	for (const CCOMClass * pClass = CCOMClass::s_pFirst; pClass != NULL; pClass = pClass->m_pNext)
		_UnregisterCOMClass (pClass->m_strProgId, pClass->m_strCLSID);
}

////////////////////////////////////////////////////

CRegisterClassFactories::CRegisterClassFactories () {
	USES_CONVERSION;
	for (CCOMClass * pClass = CCOMClass::s_pFirst; pClass != NULL; pClass = pClass->m_pNext) {
		CLSID clsid;
		::CLSIDFromProgID (A2OLE (pClass->m_strProgId), & clsid);

		CComPtr<IClassFactory> pClassFactory = new _CComClassFactory (pClass->m_pfCreateCOMObject);
		const HRESULT hr = ::CoRegisterClassObject (clsid, pClassFactory, CLSCTX_LOCAL_SERVER, pClass->m_regcls | REGCLS_SUSPENDED, & pClass->m_dwRegisterClassObject);
		assert (SUCCEEDED (hr));
	}

	::CoResumeClassObjects ();
}

CRegisterClassFactories::~CRegisterClassFactories () {
	::CoSuspendClassObjects ();

	for (const CCOMClass * pClass = CCOMClass::s_pFirst; pClass != NULL; pClass = pClass->m_pNext)
		::CoRevokeClassObject (pClass->m_dwRegisterClassObject);
}

////////////////////////////////////////////////////////

CCOMClass * CCOMClass::s_pFirst;

CCOMClass::CCOMClass (const char * _strProgId, const char * _strCLSID, pfCreateCOMObject_t _pfCreateCOMObject, UINT _regcls) :
	m_strProgId (_strProgId),
	m_strCLSID (_strCLSID),
	m_pfCreateCOMObject (_pfCreateCOMObject),
	m_regcls (_regcls),
	m_dwRegisterClassObject (0)
{
	// Insert to the head of the list.
	m_pNext = CCOMClass::s_pFirst;
	CCOMClass::s_pFirst = this;
}

////////////////////////////////////////////////////////

void LoadTypeInfo (const GUID & _libid, int _iVerMajor, int _iVerMinor, const IID & _iid, ITypeInfo ** _ret) {
	* _ret = NULL;

	CComPtr<ITypeLib> pTypeLib;
	::LoadRegTypeLib (_libid, _iVerMajor, _iVerMinor, 0, & pTypeLib);

	if (pTypeLib)
		pTypeLib->GetTypeInfoOfGuid (_iid, _ret);
}



